﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using Microsoft.Build.Utilities;
using CommonTools;
using CommonTools.Extensions;

namespace AssemblySigner
{
    public partial class AssemblySignForm : Form
    {
        private AssemblyHelper _assemblyHelper;
        private TFHelper _tfHelper;

        private Properties.Settings Settings
        {
            get { return Properties.Settings.Default; }
        }

        public AssemblySignForm()
        {
            InitializeComponent();
            listViewAssemblies.SetPropertyReflection("DoubleBuffered", true);

            _assemblyHelper = new AssemblyHelper();
            _tfHelper = btnTFS.TFHelper;

            if (_tfHelper.TFPath.IsEmpty())
                btnTFS.Enabled = false;

            _tfHelper.RaiseExceptions = false;
            _assemblyHelper.RaiseExceptions = true;

            _tfHelper.OutputChanged += ReportProgressBackground;
            _assemblyHelper.OutputChanged += ReportProgressBackground;
        }

        private void ReportProgressBackground(string output, bool error)
        {
            if (bgwSign.IsBusy)
                bgwSign.ReportProgress(error ? -1 : 0, output);
        }

        private void AssemblySignForm_Load(object sender, EventArgs e)
        {
            txtOutPath.Text = Settings.LastOutDir;

            comboKeyFiles.Keys = Settings.KeyFiles.Split(';');
            comboKeyFiles.SetKeyFile(Settings.LastSelectedKey);

            _tfHelper.SupportedCommands = (TFCommands)Settings.TFCommands;
            btnTFS.SetSupportedCommands();
            chkCreateBackup.Checked = Settings.CreateBackup;

            comboFramework.DisplayMember = "Value";
            comboFramework.ValueMember = "Key";
            comboFramework.DataSource = AssemblyHelper.GetSupportedFrameworkVersions().ToList();
            comboFramework.SelectedIndex = comboFramework.Items.Count - 1;

            CheckButtonsEnabled();
        }

        private void btnOpenAssemblies_Click(object sender, EventArgs e)
        {
            if (dlgOpenAssemblies.ShowDialog(this) == DialogResult.OK)
                AddFilesToListView(dlgOpenAssemblies.FileNames);
        }

        private void listViewAssemblies_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.None;

            if (e.Data.GetDataPresent(DataFormats.FileDrop) && !_locked)
            {
                string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];

                foreach (string file in files)
                {
                    if (AssemblyHelper.IsValidClrAssembly(file))
                        e.Effect = DragDropEffects.All;
                }
            }
        }

        private void listViewAssemblies_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];

            AddFilesToListView(files);
        }

        private void AddFilesToListView(string[] files)
        {
            listViewAssemblies.BeginUpdate();
            
            foreach (string fileName in files)
                if (AssemblyHelper.IsValidClrAssembly(fileName)) 
                    AddListViewItem(fileName);

            listViewAssemblies.EndUpdate();

            listViewAssemblies.Focus();
        }

        private ListViewItem ReplaceListViewItem(ListViewItem item, string newAssemblyFile)
        {
            var newItem = NewListViewItem(newAssemblyFile);

            listViewAssemblies.BeginUpdate();
            listViewAssemblies.Items[item.Index] = newItem;
            ResizeColumns();
            listViewAssemblies.EndUpdate();

            return newItem;
        }

        private ListViewItem AddListViewItem(string assemblyFile)
        {
            var item = GetListViewItem(assemblyFile);
            
            if (item == null)
            {
                item = listViewAssemblies.Items.Add(NewListViewItem(assemblyFile));

                ResizeColumns();
                CheckButtonsEnabled();
            }
            else 
                item.Selected = true;

            return item;
        }

        private ListViewItem GetListViewItem(string assemblyFile)
        {
            foreach (ListViewItem item in listViewAssemblies.Items)
                if (item.SubItems["FullPath"].Text == assemblyFile)
                    return item;

            return null;
        }

        private ListViewItem NewListViewItem(string assemblyFile)
        {
            var newItem = new ListViewItem();
            newItem.UseItemStyleForSubItems = false;

            newItem.SubItems.Clear();
            newItem.Text = Path.GetFileName(assemblyFile);

            AssemblyInfo assemblyInfo = AssemblyHelper.GetAssemblyInfo(assemblyFile);

            if (assemblyInfo.IsSigned)
                newItem.SubItems.Add("Yes").ForeColor = SUCCESS_COLOR;
            else
                newItem.SubItems.Add("No").ForeColor = ERROR_COLOR;

            newItem.SubItems.AddRange(new string[]
            {
                assemblyInfo.Version,
                assemblyInfo.RuntimeVersion,
                assemblyInfo.Platform,
                assemblyInfo.PublicKeyToken
            });

            var subItemPath = newItem.SubItems.Add(assemblyFile);
            subItemPath.Name = "FullPath";
            subItemPath.Tag = assemblyFile;

            return newItem;
        }


        private void ResizeColumns()
        {
            colAssemblyName.Width = LISTVIEW_AUTOSIZE_HEADER_AND_CONTENT;
            colAssemblyVersion.Width = LISTVIEW_AUTOSIZE_HEADER_AND_CONTENT;
            colPublicKeyToken.Width = LISTVIEW_AUTOSIZE_HEADER_AND_CONTENT;
        }
        private const int LISTVIEW_AUTOSIZE_HEADER_AND_CONTENT = -2;

        private string SelectedKeyFile
        {
            get { return comboKeyFiles.SelectedKeyFile; }
            set { comboKeyFiles.SelectedKeyFile = value; }
        }

        private void CheckButtonsEnabled()
        {
            btnSign.Enabled = !string.IsNullOrEmpty(SelectedKeyFile) && CurrentItems.Count > 0 && !_locked;
            btnVerify.Enabled = CurrentItems.Count > 0 && !_locked;
        }

        private void btnOutDirectory_Click(object sender, EventArgs e)
        {
            dlgOutputFolder.SelectedPath = Properties.Settings.Default.LastOutDir;

            if (dlgOutputFolder.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
            {
                txtOutPath.Text = dlgOutputFolder.SelectedPath;

                Settings.LastOutDir = dlgOutputFolder.SelectedPath;
                Settings.Save();
            }
        }

        private void AddToLog(string message)
        {
            AddToLog(message, txtLog.ForeColor);
        }

        private void AddToLog(string message, Color color)
        {
            txtLog.AppendText(message, color);
            txtLog.SelectionStart = txtLog.Text.Length;
            txtLog.ScrollToCaret();
        }

        private bool _locked = false;
        private void SwitchWaitInterface(bool value)
        {
            _locked = value;
            progressBar.Visible = lblWait.Visible = btnCancel.Visible = _locked;
            pnlDirectories.Enabled = btnOpen.Enabled = btnSign.Enabled = btnVerify.Enabled = !_locked;
            //listViewAssemblies.Enabled = !value;
            progressBar.Value = 0;
        }

        private IList CurrentItems
        {
            get
            {
                return listViewAssemblies.SelectedItems.Count > 0 ?
                    listViewAssemblies.SelectedItems.ToList() : listViewAssemblies.Items.ToList();
            }
        }

        private void btnSign_Click(object sender, EventArgs e)
        {
            List<string> assemblies = new List<string>();

            foreach (ListViewItem item in CurrentItems)
                assemblies.Add(item.SubItems["FullPath"].Text);

            txtLog.Clear();

            SwitchWaitInterface(true);

            bgwSign.RunWorkerAsync(assemblies.ToArray());
        }

        private void bgwSign_DoWork(object sender, DoWorkEventArgs e)
        {
            string[] assemblyFiles = e.Argument as string[];

            StringBuilder errors = new StringBuilder();

            int filesProcessed = 0;

            foreach (string assemblyFile in assemblyFiles)
            {
                if (bgwSign.CancellationPending)
                {
                    e.Cancel = true;
                    break;
                }

                string outAssemblyPath = "";
                try
                {
                    _tfHelper.Get(assemblyFile);
                    _tfHelper.CheckOut(assemblyFile);

                     outAssemblyPath = _assemblyHelper.Sign(
                         assemblyFile, comboKeyFiles.SelectedKeyFile, txtOutPath.Text, chkCreateBackup.Checked);

                    _tfHelper.CheckIn(assemblyFile);
                }
                catch (Exception ex)
                {
                }

                if (_assemblyHelper.Error != "")
                    errors.Append(Path.GetFileName(assemblyFile) + Environment.NewLine);

                filesProcessed++;

                bgwSign.ReportProgress(
                    percentProgress: (filesProcessed * 100) / assemblyFiles.Length,
                    userState: new string[] { assemblyFile, outAssemblyPath, _assemblyHelper.Error });
            }

            e.Result = errors;
        }

        private void bgwSign_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage > 0)
                progressBar.Value = e.ProgressPercentage;

            if (e.UserState is string[])
            {
                var progressInfo = e.UserState as string[];

                string assemblyFile = progressInfo[0];
                string outAssemblyPath = progressInfo[1];
                string error = progressInfo[2];

                var assemblyItem = GetListViewItem(assemblyFile);

                if (outAssemblyPath.NotEmpty())
                    assemblyItem = ReplaceListViewItem(assemblyItem, outAssemblyPath);

                if (error.NotEmpty())
                    assemblyItem.SetSubItem("Info", error, ERROR_COLOR);
                else
                    assemblyItem.SetSubItem("Info", "Assembly was signed successfully", SUCCESS_COLOR);
            }
            else if (e.UserState != null)
            {
                bool isError = e.ProgressPercentage < 0;
                AddToLog(e.UserState.ToString(), isError ? ERROR_COLOR : txtLog.ForeColor);
            }
        }

        private Color SUCCESS_COLOR = Color.Green;
        private Color ERROR_COLOR = Color.Red;

        private void bgwSign_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            SwitchWaitInterface(false);

            if (e.Cancelled)
            {
                MessageBox.Show("Operation Cancelled", "Operation Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                string errors = e.Result.ToString();

                if (errors != "")
                {
                    string errMessage = string.Format("Error signing assemblies{1}{0}{1}See info for details.", errors, Environment.NewLine);
                    MessageBox.Show(errMessage, "Completed with errors", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                    MessageBox.Show("Completed Successfully", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            bgwSign.CancelAsync();
        }

        private void btnVerify_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem item in CurrentItems)
            {
                string assemblyFile = item.SubItems["FullPath"].Text;

                if (AssemblyHelper.VerifyStrongName(assemblyFile))
                {
                    string info = string.Format("Assembly {0} is valid\r\n", assemblyFile);
                    AddToLog(info, SUCCESS_COLOR);
                    item.SetSubItem("Info", info, SUCCESS_COLOR);
                }
                else
                {
                    string errorInfo = _assemblyHelper.SnVerify(assemblyFile);
                    item.SetSubItem("Info", errorInfo, ERROR_COLOR);
                    AddToLog(errorInfo, ERROR_COLOR);
                }
            }
        }

        private void listViewAssemblies_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete && !_locked)
            {
                foreach (ListViewItem item in listViewAssemblies.SelectedItems)
                    listViewAssemblies.Items.Remove(item);
            }
            if (e.KeyCode == Keys.A && e.Control)
            {
                foreach (ListViewItem item in listViewAssemblies.Items)
                    item.Selected = true;
            }

            CheckButtonsEnabled();
        }

        private void listViewAssemblies_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            CheckButtonsEnabled();
        }

        private void comboKeyFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(SelectedKeyFile))
                txtPublicKeyToken.Text = AssemblyHelper.GetPublicKeyToken(comboKeyFiles.SelectedKeyFile);
            else
                txtPublicKeyToken.Text = "";
        }

        private void AssemblySignForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            Settings.TFCommands = (int)_tfHelper.SupportedCommands;
            Settings.KeyFiles = string.Join(";", comboKeyFiles.Keys);
            Settings.LastSelectedKey = comboKeyFiles.SelectedKeyFile;
            Settings.CreateBackup = chkCreateBackup.Checked;

            Settings.Save();
        }

        private void comboFramework_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (comboFramework.SelectedValue is TargetDotNetFrameworkVersion)
                _assemblyHelper.TargetFramework = (TargetDotNetFrameworkVersion)comboFramework.SelectedValue;
        }

        private void btnClearLog_Click(object sender, EventArgs e)
        {
            txtLog.Clear();
        }

        private ToolTip subItemToolTip = new ToolTip();
        private Point _lastMousePos = new Point(-1, -1);

        private void listViewAssemblies_MouseMove(object sender, MouseEventArgs e)
        {
            if (_lastMousePos != e.Location)
            {
                var subItem = listViewAssemblies.HitTest(e.Location).SubItem;

                if (subItem != null && subItem.Tag != null)
                {
                    if (subItemToolTip.Tag != subItem)
                    {
                        GetToolTipForSubItem(subItem);
                        subItemToolTip.Show(subItem.Tag.ToString(), listViewAssemblies, subItem.Bounds.Location);
                        subItemToolTip.Tag = subItem;
                    }
                }
                else
                {
                    subItemToolTip.Hide(listViewAssemblies);
                    subItemToolTip.Tag = null;
                }
            }
            _lastMousePos = e.Location;
        }

        private void GetToolTipForSubItem(ListViewItem.ListViewSubItem subItem)
        {
            if (subItem.ForeColor == ERROR_COLOR)
            {
                subItemToolTip.ToolTipIcon = ToolTipIcon.Error;
                subItemToolTip.ToolTipTitle = "Error";
            }
            else if (subItem.ForeColor == SUCCESS_COLOR)
            {
                subItemToolTip.ToolTipIcon = ToolTipIcon.Info;
                subItemToolTip.ToolTipTitle = "Info";
            }
            else
            {
                subItemToolTip.ToolTipIcon = ToolTipIcon.None;
                subItemToolTip.ToolTipTitle = "";
            }
        }
    }

    public static class ListViewExtensions
    {
        public static void SetSubItem(this ListViewItem item, string name, string text, Color foreColor)
        {
            var subItem = item.SubItems[name];

            if (subItem == null)
                subItem = item.SubItems.Add(text);

            subItem.Name = name;
            subItem.Text = text;
            subItem.Tag = text;
            subItem.ForeColor = foreColor;
        }

        public static void AppendText(this RichTextBox textBox, string text, Color color)
        {
            textBox.SelectionStart = textBox.TextLength;
            textBox.SelectionLength = 0;

            textBox.SelectionColor = color;
            textBox.AppendText(text);
            textBox.SelectionColor = textBox.ForeColor;
        }

        public static IList ToList(this IEnumerable col)
        {
            var list = new ArrayList();
            foreach (object obj in col)
                list.Add(obj);
            return list;
        }
    }

    
}
